/*
 * ThreadedDotProduct.cpp
 *
 *  Created on: Feb 3, 2010
 *      Author: Jamie LaPointe
 *
 *       Notes: Original code is in C and located at:
 *              https://computing.llnl.gov/tutorials/pthreads/#Pthread
 */
#include "ThreadedDotProduct.h"

using namespace MutexExample;

void * MutexExample::computeDotProduct(void * arg)
{
   int index = 0;
   int start = 0;
   int end = 0;
   int length = 0;
   long offset = 0;
   double * x = 0;
   double * y = 0;
   double mySum = 0.0;
   DotArg * dotArg = (DotArg *)arg;

   offset = dotArg->index;
   length = dotArg->dotData->vectorLength;
   start = offset * length;
   end = start + length;
   x = dotArg->dotData->a;
   y = dotArg->dotData->b;

   //
   // Perform the dot product and assign result to the appropriate
   // variable in the structure.
   //
   for (index = start; index < end; ++index)
   {
      mySum += (x[index] * y[index]);
   }

   //
   // Lock a mutex prior to updating the value in the shared structure,
   // and unlock it when finished.
   //
   // This is the critical region which needs to be protected by a SHARED mutex.
   //
   pthread_mutex_lock(dotArg->mutexSum);
   dotArg->dotData->sum += mySum;
   pthread_mutex_unlock(dotArg->mutexSum);

   pthread_exit(0);
   return 0;
}

double ThreadedDotProduct::run()
{
   long index;
   double * a = 0;
   double * b = 0;
   void * status = 0;
   pthread_attr_t threadAttr;

   //
   // Assign storage
   //
   a = (double *)malloc(numThreads_ * vectorLength_ * sizeof(double));
   b = (double *)malloc(numThreads_ * vectorLength_ * sizeof(double));

   //
   // Initialize values
   //
   for (index = 0; index < (vectorLength_ * numThreads_); ++index)
   {
      a[index] = 1.0;
      b[index] = a[index];
   }

   dotData_.vectorLength = vectorLength_;
   dotData_.a = a;
   dotData_.b = b;
   dotData_.sum = 0;

   //
   // Setup the mutex
   //
   pthread_mutex_init(&mutexSum_, NULL);

   //
   // Create threads to run the dot product
   // We explicitly state the thread to be joinable (it should be by default anyway)
   //
   // Normally I would use other synchronization methods (e.g. counting semaphore)
   // instead of thread joining, but this example is only meant to demonstrate the
   // use of a mutex.
   //
   pthread_attr_init(&threadAttr);
   pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_JOINABLE);

   //
   // Setup the argument with data needed by the new thread; note that we are passing
   // the shared dotData structure to the new threads and a shared mutex variable; the
   // mutex variable MUST be the same actual object for each thread to function properly.
   //
   dotArg_.dotData = &dotData_;
   dotArg_.mutexSum = &mutexSum_;

   //
   // Create and send each thread off to do its dirty work.
   //
   for (index = 0; index < numThreads_; ++index)
   {
      //
      // Each thread works on a different set of data.  The offset is specified by
      // 'index'.  The size of the data for each thread is indicated by 'vectorLength_',
      // which is stored in the dotData structure of our dotArg structure.
      //
      dotArg_.index = index;
      pthread_create(&callThread_[index], &threadAttr, computeDotProduct, (void *)&dotArg_);
   }

   //
   // Destroy the thread attribute to free system resources.
   //
   pthread_attr_destroy(&threadAttr);

   //
   // Wait on the other threads to exit (see above note on Joinable threads...)
   //
   for (index = 0; index < numThreads_; ++index)
   {
      pthread_join(callThread_[index], &status);
   }

   //
   // After joining, cleanup local malloc variables and return data.
   //
   free(a);
   a = 0;
   free(b);
   b = 0;

   // using the default constructor, you should get 400.0 :-)
   return dotData_.sum;
}
